<template>
    <div class="calendarToggle" :style="{ '--latticeHeight': props.latticeHeight + 'px' }">
        <div class="calendarToggle-header">
            <slot name="header" :data="selfTime">
                {{ selfTime }}
            </slot>
        </div>


        <!-- 多加一层用于拖拽鼠标按下事件拖拽 -->
        <div class="moveLayer" @mousedown="handleMouseDown" @mouseup="handleMouseUp" @touchstart="handleTouchStart"
            @touchend="handleTouchEnd">
            <div class="calendarToggle-week">
                <div class="calendarToggle-week-item" :key="'calendarToggle-week-item' + index"
                    v-for="text, index in weeks[props.startWeek][props.weekType]">
                    <slot name="week" :data="text" :index="index">
                        {{ text }}
                    </slot>
                </div>
            </div>

            <!-- boxHeight的css属性用默认值+滑动区域的移动值,动态修改容器高度; 用的定位高度不会撑高;设置的 -->
            <div class="calendarToggle-day" ref="daybox"
                :style="{ '--boxHeight': (boxHeight + mouseDetail.moveY) + 'px', '--boxHeightToggle': -(boxHeight - props.latticeHeight) + 'px' }"
                :class="{ 'toggle': props.visible }">
                <div class="calendarToggle-day-layer" ref="layer">
                    <div class="calendarToggle-day-row"
                        :class="{ 'calendarToggle-day-row_active': active_row == (key + 1) }"
                        :key="'calendarToggle-day-row' + key" v-for="row, key in forDays">
                        <div class="calendarToggle-day-row-column" v-for="item, ikey in row"
                            :key="'calendarToggle-day-row-column' + ikey"
                            @dblclick="handleDblClickDayItem($event, key, item)">
                            <slot name="day" :data="item" :index="ikey" :row="row">
                                <div class="calendarToggle-day-row-column-daytext"
                                    :class="{ 'calendarToggle-day-row-column-active': item.active, 'calendarToggle-day-row-column-click': item.dateStr == selfTime, 'calendarToggle-day-row-column-low': item.status < 1 }">
                                    {{ item.day }}</div>
                            </slot>
                        </div>
                    </div>
                </div>
            </div>

            <slot name="toggle">
                <div class="calendarToggle-toggle">
                    <span class="calendarToggle-toggle-text" @click="handleToggle">{{ props.visible ? '展开' : '收起' }}</span>
                </div>
            </slot>
        </div>


</div>
</template>

<script setup lang="ts">


import type { forDaysI, dayI } from './interfaces';
import formatDate from '../../tool/formatDate';
import { ref, defineEmits, defineProps, watch, reactive, nextTick } from 'vue';
let emits = defineEmits(['onDblClick', 'update:date', 'update:visible']);
interface weeksI {
    '1': {
        'cn': Array<String>,
        'en': Array<String>
    },
    '2': {
        'cn': Array<String>,
        'en': Array<String>
    }
}
let weeks: weeksI = reactive({
    '1': {
        'cn': ['一', '二', '三', '四', '五', '六', '日'],
        'en': ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    '7': {
        'cn': ['日', '一', '二', '三', '四', '五', '六'],
        'en': ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
    }
});
let count = 7 * 5;
let props = defineProps({
    'date': {
        type: Date,
        default: () => new Date()
    },
    'data': {
        type: Array,
        default: () => []
    },
    'startWeek': {
        type: Number,
        default: () => 1,
    },
    'detailkey': {
        type: String,
        default: () => 'date'
    },
    'latticeHeight': {
        type: Number,
        default: () => 46
    },
    "isSixRow": {
        type: Boolean,
        default: () => true
    },
    "visible": {
        type: Boolean,
        default: () => true
    },
    "weekType": {
        type: String,
        default: () => 'cn'
    }
});
let boxHeight = ref();
let selfTime = ref();
let active_row = ref();
let forDays: Array<Array<forDaysI>> = reactive([]);
const handleClickDay = (item: Object) => {
    emits('onDblClick', item);
}
const handleDblClickDayItem = (event: Event, key: number, item: forDaysI) => {
    let year = item.year;
    let month = item.month;
    let day = item.day;
    let date = new Date(year, month, day);
    if (props.visible) {
        // 如果为收起状态去掉过渡效果切换日期
        noTransition();
    }
    emits('update:date', date);
    emits('onDblClick', item, date);
}

// 鼠标操作的记录
let mouseDetail = reactive({
    startY: 0,
    endY: 0,
    moveY: 0
});
// 收起缩放切换
const handleToggle = () => {
    emits('update:visible', !props.visible);
    // 格子点击事件 true（收起）时候设置的切换上月下月过渡效果取消
    // 这里每次展开恢复
    // 改为watch,因为底部部分可以用插槽;同时使用组件时可以通过修改v-model:visible='var1' var1控制显隐
    // 用监听可以省略以下代码
    // nextTick(() => {
    //     if (!props.visible) {
    //         daybox.value.style.transition = '.35s 0s height linear';
    //         layer.value.removeAttribute('style');
    //         daybox.value.classList.remove('clearDayopacity-transition');
    //     }
    // })
}
// 滑动区域鼠标移动事件
const handleMouseMove = (event: MouseEvent) => {
    // 由于通过toggle(visible)添加的transition过渡;动态修改top会有延迟的过渡效果每次 鼠标移动设置none
    layer.value.style.transition = 'none';
    daybox.value.style.transition = 'none';
    // 通过鼠标按下位置和当前移动位置获得移动的距离
    let moveY = event.pageY - mouseDetail.startY;
    let dif = event.pageY - mouseDetail.startY;

    // true收起时
    if (props.visible) {
        // 收起时上滑 no
        if (dif < 0) {
            moveY = 0;
        }
        moveY = -(boxHeight.value - props.latticeHeight - moveY);
        let daybox_height = (boxHeight.value + moveY);
        if (daybox_height > boxHeight.value) {
            daybox_height = boxHeight.value;
            emits('update:visible', false);
        }
        [...layer.value.querySelectorAll('.calendarToggle-day-row')].map(d => {
            d.classList.add('noopacity-calendarToggle-day-row');
        })
        daybox.value.style.height = daybox_height + 'px';
    } else {
        // 滑动中如果已经到最顶部就让toggle(visible)为true收起状态
        if (moveY < -(boxHeight.value - props.latticeHeight)) {
            moveY = -(boxHeight.value - props.latticeHeight);
            emits('update:visible', true);
        }
        if (dif > 0) {
            moveY = 0;
        }
    }
    mouseDetail.moveY = moveY;
    // 移动layer层
    if (mouseDetail.moveY < 0) {
        layer.value.style.top = mouseDetail.moveY + 'px';
    }
    event.preventDefault();
}
let layer = ref();
let daybox = ref();
// window窗口的鼠标抬起事件
const windowUp = (event: MouseEvent) => {
    // 窗口抬起也加上判断防止 超出容器范围不触发绑定的up事件
    mouseDetail.endY = event.pageY;
    // 获取开始位置 与 结束位置的 差
    let difference = mouseDetail.startY - mouseDetail.endY;
    if (difference > props.latticeHeight) {
        emits('update:visible', true)
    } else if (difference < -(props.latticeHeight * .15)) {
        emits('update:visible', false)
    }

    // 每次抬起删除layer绑定的 move事件
    layer.value?.removeEventListener('mousemove', handleMouseMove);

    // 初始css设置的transition
    initTransition();

    // 初始mouseDetail
    mouseDetail.startY = 0;
    mouseDetail.endY = 0;
    mouseDetail.moveY = 0;
    daybox.value.style.height = null;
    [...layer.value.querySelectorAll('.calendarToggle-day-row')].map(d => {
        d.classList.remove('noopacity-calendarToggle-day-row');
    })
    // 删除window的mouseup事件
    window.removeEventListener('mouseup', windowUp);
}

// 滑动区域鼠标按下事件
const handleMouseDown = (event: MouseEvent) => {
    // 每次按下记录 当前位置
    mouseDetail.startY = event.pageY;
    // 绑定move事件
    layer.value?.addEventListener('mousemove', handleMouseMove);
    // 绑定window的抬起事件
    window.addEventListener('mouseup', windowUp);
}

// 滑动区域鼠标抬起事件
const handleMouseUp = (event: MouseEvent) => {
    mouseDetail.endY = event.pageY;
    // 获取开始位置 与 结束位置的 差
    let difference = mouseDetail.startY - mouseDetail.endY;
    if (difference > props.latticeHeight) {
        emits('update:visible', true)
    } else if (difference < -(props.latticeHeight * .15)) {
        emits('update:visible', false)
    }
}

const handleTouchMove = (event: TouchEvent) => {
    // 由于通过toggle(visible)添加的transition过渡;动态修改top会有延迟的过渡效果每次 鼠标移动设置none
    layer.value.style.transition = 'none';
    daybox.value.style.transition = 'none';
    // 通过鼠标按下位置和当前移动位置获得移动的距离
    let moveY = event.targetTouches[0].clientY - mouseDetail.startY;
    let dif = event.targetTouches[0].clientY - mouseDetail.startY;

    // true收起时
    if (props.visible) {
        // 收起时上滑 no
        if (dif < 0) {
            moveY = 0;
        }
        moveY = -(boxHeight.value - props.latticeHeight - moveY);
        let daybox_height = (boxHeight.value + moveY)
        if (daybox_height > boxHeight.value) {
            daybox_height = boxHeight.value;
            emits('update:visible', false);
        }
        [...layer.value.querySelectorAll('.calendarToggle-day-row')].map(d => {
            d.classList.add('noopacity-calendarToggle-day-row');
        })
        daybox.value.style.height = daybox_height + 'px';
    } else {
        // 滑动中如果已经到最顶部就让toggle(visible)为true收起状态
        if (moveY < -(boxHeight.value - props.latticeHeight)) {
            moveY = -(boxHeight.value - props.latticeHeight);
            emits('update:visible', true)
        }
        if (dif > 0) {
            moveY = 0;
        }
    }
    mouseDetail.moveY = moveY;
    // 移动layer层
    if (mouseDetail.moveY < 0) {
        layer.value.style.top = mouseDetail.moveY + 'px';
    }
    event.preventDefault();
}
const docTouchEnd = (event: TouchEvent) => {
    // 窗口抬起也加上判断防止 超出容器范围不触发绑定的up事件
    mouseDetail.endY = event.changedTouches[0].clientY;
    // 获取开始位置 与 结束位置的 差
    let difference = mouseDetail.startY - mouseDetail.endY;
    if (difference > props.latticeHeight) {
        emits('update:visible', true)
    } else if (difference < -(props.latticeHeight * .15)) {
        emits('update:visible', false)
    }

    // 每次抬起删除layer绑定的 touchmove
    layer.value?.removeEventListener('touchmove', handleTouchMove);

    // 初始css设置的transition
    initTransition();
    // 初始mouseDetail
    mouseDetail.startY = 0;
    mouseDetail.endY = 0;
    mouseDetail.moveY = 0;
    daybox.value.style.height = null;
    [...layer.value.querySelectorAll('.calendarToggle-day-row')].map(d => {
        d.classList.remove('noopacity-calendarToggle-day-row');
    })
    // 删除window的mouseup事件
    window.removeEventListener('touchend', docTouchEnd);
}
const handleTouchStart = (event: TouchEvent) => {
    mouseDetail.startY = event.targetTouches[0].clientY;
    layer.value?.addEventListener('touchmove', handleTouchMove);
    document.body.addEventListener('touchend', docTouchEnd);
}
const handleTouchEnd = (event: TouchEvent) => {
    mouseDetail.endY = event.changedTouches[0].clientY;
    // 获取开始位置 与 结束位置的 差
    let difference = mouseDetail.startY - mouseDetail.endY;
    if (difference > props.latticeHeight) {
        emits('update:visible', true)
    } else if (difference < -(props.latticeHeight * .15)) {
        emits('update:visible', false)
    }
}

const buLing = (n: number) => {
    if (n < 10) {
        return '0' + n;
    } else {
        return n;
    }
}
const isLeapYear = (year: number) => {
    if (((year % 4) == 0) && ((year % 100) != 0) || ((year % 400) == 0)) {
        return 1;
    } else {
        return 0;
    }
}
const forDaysMethods = (date: Date = new Date(), list: Object = []) => {
    let { startWeek, detailkey } = props;
    let oDate = new Date(date);
    let year = (oDate.getFullYear())                   //获取年  
    let month = (oDate.getMonth())                     //获取月
    let day = (oDate.getDate())                        //获取当日时间
    let days = ([31, 28 + isLeapYear(year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]);       //所有月份的天数,这里条用的一个函数用于计算闰年
    let firstDayLeft = (new Date(year, month, 1)).getDay();
    let firstDay = (firstDayLeft == 0 ? 7 : firstDayLeft);
    selfTime.value = formatDate(date);
    let hovers: any = (list)             //父传子的参数,用于设置任务 活动悬浮效果

    let start_y = oDate.getFullYear();
    let end_y = oDate.getFullYear();
    let start_m = oDate.getMonth();
    let end_m = oDate.getMonth();

    let start = <dayI[]>([])             //用于计算 开头
    start_m -= 1;
    if (start_m < 0) {
        start_m = 11;
        start_y = start_y - 1;
    }

    let start_i_minus = 1;
    // 判断日历 周第一个是 周日 还是 周一
    if (startWeek === 1) {
        start_i_minus = 1;
        // weeks = ['一', '二', '三', '四', '五', '六', '日']
    }
    if (startWeek === 7) {
        start_i_minus = 0;
        // weeks = ['日', '一', '二', '三', '四', '五', '六']
    }

    if (startWeek != 7 && startWeek != 1) {
        throw new Error('startWeek只能传入1和7');//Uncaught Error: test
        return false;
    }

    for (let i = 1; i <= days[start_m]; i++) {

        if (i - start_i_minus > days[start_m] - firstDay) {
            start.push({
                status: -1,
                day: i,
                active: false,
                year: start_y,
                month: start_m,
            })
        }
    }

    let center = <dayI[]>([])             //计算中间 当前月时间
    for (let i = 1; i <= days[month]; i++) {
        let itemDay = {
            status: 1,
            day: i,
            active: false,
            year: year,
            month: month,
        }
        if (day == i) {
            itemDay.active = true
            active_row.value = Math.ceil((i + start.length) / 7);
        } else {
            itemDay.active = false
        }
        center.push(itemDay)
    }

    // 控制是否显示6行不变
    if (props.isSixRow) {
        count = 7 * 6;
        boxHeight.value = props.latticeHeight * 6;
    } else {
        if ((start.length + center.length) / 7 >= 5) {
            count = 7 * 6;
            boxHeight.value = props.latticeHeight * 6;
        } else {
            count = 7 * 5;
            boxHeight.value = props.latticeHeight * 5;
        }
    }

    let end = <dayI[]>([])         //计算尾部
    end_m += 1;
    if (end_m > 11) {
        end_m = 0;
        end_y = end_y + 1;
    }
    let end_days = count - (start.length + center.length)
    for (let i = 0; i < end_days; i++) {
        end.push({
            status: 0,
            day: i + 1,
            active: false,
            year: end_y,
            month: end_m,
        })
    }
    let all = <forDaysI[]>(start.concat(center).concat(end));
    let new_all: Array<Array<forDaysI>> = [];
    all.forEach((item: forDaysI, key) => {
        let dateStr = item.year + '-' + buLing(item.month + 1) + '-' + buLing(item.day);
        item['dateStr'] = dateStr;
        hovers.forEach((hover: any) => {
            if (hover[detailkey] == dateStr) {
                item['detail'] = hover;
            }
        })
        if (key % 7 == 0 && key >= 0) {
            new_all.push([])
        }
        new_all[new_all.length - 1].push(item);
    })
    forDays.length = 0;
    forDays.push(...new_all);
}
forDaysMethods(props.date, props.data);
watch([() => props.date, () => props.startWeek], () => {
    // 监听传入的日期变化如果visible为true(收起状态去掉过渡效果);
    if (props.visible) {
        noTransition();
    }
    forDaysMethods(props.date, props.data);
})
watch(props.data, () => {
    forDaysMethods(props.date, props.data);
})

// toggle事件修改的 改为监听;因为此按钮可以自定义;可以不添加内容;其他方式控制显示隐藏
watch(() => props.visible, (n) => {
    nextTick(() => {
        if (!props.visible) {
            initTransition();
        }
    })
})

const initTransition = () => {
    // 删除由mouse事件修改的style
    layer.value?.removeAttribute('style');
    // 过渡初始到 css设置的
    daybox.value.style.transition = '.35s 0s height linear';
    // 格子点击事件 true（收起）时候设置的切换上月下月过渡效果取消
    // 这里每次展开恢复
    daybox.value.classList.remove('clearDayopacity-transition');
}
const noTransition = () => {
    // 如果为收起状态切换日期;去掉过渡
    daybox.value.style.transition = 'height 0s linear';
    layer.value.style.transition = 'none';
    daybox.value.classList.add('clearDayopacity-transition');
}
</script>

<style scoped>
.calendarToggle {
    width: 100%;
    height: auto;
}

.calendarToggle-header {
    height: calc(var(--latticeHeight) * .75);
    line-height: calc(var(--latticeHeight) * .75);
    text-align: center;
    font-weight: 600;
    font-size: 14px;
}

.calendarToggle-toggle {
    width: 100%;
    height: 16px;
    line-height: 16px;
    text-align: center;
    margin-top: 6px;
}
.calendarToggle-toggle .calendarToggle-toggle-text {
    user-select: none;
    font-size: 14px;
    color: #333;
}

.calendarToggle-week {
    height: calc(var(--latticeHeight) * .75);
    display: flex;
}

.calendarToggle-week-item {
    flex: 1;
    line-height: calc(var(--latticeHeight) * .75);
    text-align: center;
    font-size: 14px;
    user-select: none;
    font-weight: 800;
    /* 微软雅黑 中文 一 二 三 加粗效果失效 */
    font-family: 黑体;
}

.calendarToggle-day {
    width: 100%;
    height: var(--boxHeight);
    position: relative;
    overflow: hidden;
    transition: .35s 0s height linear;
}

.calendarToggle-day-row {
    height: var(--latticeHeight);
    display: flex;
}

.calendarToggle-day-row-column {
    height: 100%;
    line-height: var(--latticeHeight);
    text-align: center;
    flex: 1;
    display: flex;
}

.calendarToggle-day-row-column .calendarToggle-day-row-column-daytext {
    width: calc(var(--latticeHeight) * .65);
    height: calc(var(--latticeHeight) * .65);
    text-align: center;
    line-height: calc(var(--latticeHeight) * .65 - 2px);
    margin: auto;
    border: 1px solid transparent;
    box-sizing: border-box;
    border-radius: 50%;
    user-select: none;
}

.calendarToggle-day-row-column .calendarToggle-day-row-column-active {
    color: #1cd4e9;
    font-weight: 800;
}

.calendarToggle-day-row-column .calendarToggle-day-row-column-click {
    border: 1px solid #1cd4e9;
}

.calendarToggle-day-row-column .calendarToggle-day-row-column-low {
    opacity: .5;
}

.calendarToggle-day-layer {
    width: 100%;
    position: absolute;
    top: 0px;
    transition: top .35s linear;
}

.toggle {
    height: var(--latticeHeight);
    transition: .35s 0s height linear;


}

.toggle .calendarToggle-day-layer {
    top: var(--boxHeightToggle);
}

.toggle .calendarToggle-day-row {
    opacity: 0;
    transition: 0s .35s opacity linear;
}

.toggle .noopacity-calendarToggle-day-row {
    opacity: 1 !important;
    transition: none !important;
}

.clearDayopacity-transition .calendarToggle-day-row {
    transition: none !important;
}

/* 固定的奥秘在这里;具体demo请看 views/demos/sa-calendarToggle-toggle/com.vue */
.calendarToggle-day-row_active {
    position: sticky;
    top: 0px;
    background: #eee;
    z-index: 9999;
    opacity: 1 !important;
}
</style>